package com.example.supplychainbackend.config;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import io.grpc.Grpc;
import io.grpc.ManagedChannel;
import io.grpc.TlsChannelCredentials;
import lombok.AllArgsConstructor;
import org.hyperledger.fabric.client.*;
import org.hyperledger.fabric.client.identity.Identities;
import org.hyperledger.fabric.client.identity.Identity;
import org.hyperledger.fabric.client.identity.Signer;
import org.hyperledger.fabric.client.identity.Signers;
import org.hyperledger.fabric.client.identity.X509Identity;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.cert.CertificateException;
import java.time.Instant;
import java.util.concurrent.TimeUnit;

@Configuration
@AllArgsConstructor
public class FabricConfig {
//    private static final String MSP_ID = System.getenv().getOrDefault("MSP_ID", "Org1MSP");

    // Path to crypto materials.
//    private static final Path CRYPTO_PATH = Paths.get("src/main/resources/org1.example.com");
    // Path to user certificate.
//    private static final Path CERT_DIR_PATH = CRYPTO_PATH.resolve(Paths.get("users/User1@org1.example.com/msp/signcerts"));
    // Path to user private key directory.
//    private static final Path KEY_DIR_PATH = CRYPTO_PATH.resolve(Paths.get("users/User1@org1.example.com/msp/keystore"));
    // Path to peer tls certificate.
//    private static final Path TLS_CERT_PATH = CRYPTO_PATH.resolve(Paths.get("peers/peer0.org1.example.com/tls/ca.crt"));

    // Gateway peer end point.
//    private static final String PEER_ENDPOINT = "localhost:40701";
//    private static final String OVERRIDE_AUTH = "peer0.org1.example.com";


    public static Gateway makeGateway(String orgNum) throws Exception {
        var channel = newGrpcConnection(orgNum);
        var builder = Gateway.newInstance()
                .identity(newIdentity(orgNum))
                .signer(newSigner(orgNum))
//                .hash(Hash.SHA256)
                .connection(channel)
                // Default timeouts for different gRPC calls
                .evaluateOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
                .endorseOptions(options -> options.withDeadlineAfter(15, TimeUnit.SECONDS))
                .submitOptions(options -> options.withDeadlineAfter(5, TimeUnit.SECONDS))
                .commitStatusOptions(options -> options.withDeadlineAfter(1, TimeUnit.MINUTES));

        return builder.connect();
    }

    public static Contract makeContract(String orgNum) throws Exception {
        Gateway gateway = makeGateway(orgNum);
        Network network = gateway.getNetwork("cn"+orgNum);
        return network.getContract("cc"+orgNum);
    }
    @Bean
    public Contract contract1() throws Exception {
        return makeContract("1");
    }
    @Bean
    public Contract contract2() throws Exception {
        return makeContract("2");
    }
    @Bean
    public Contract contract3() throws Exception {
        return makeContract("3");
    }
    @Bean
    public Contract contract4() throws Exception {
        return makeContract("4");
    }
    @Bean
    public Contract contract5() throws Exception {
        return makeContract("5");
    }
    @Bean
    public Contract contract6() throws Exception {
        return makeContract("6");
    }
    @Bean
    public Contract contract7() throws Exception {
        return makeContract("7");
    }
    @Bean
    public Contract contract8() throws Exception {
        return makeContract("8");
    }
    @Bean
    public Contract contract9() throws Exception {
        return makeContract("9");
    }

    private static ManagedChannel newGrpcConnection(String orgNum) throws IOException {
        Path cryptoPath = Paths.get("src/main/resources/org"+orgNum+".example.com");
        Path tlsCertPath = cryptoPath.resolve(Paths.get("peers/peer0.org"+orgNum+".example.com/tls/ca.crt"));
        var credentials = TlsChannelCredentials.newBuilder()
                .trustManager(tlsCertPath.toFile())
                .build();
        String peerEndpoint = "localhost:4070"+orgNum;
        String OVERRIDE_AUTH = "peer0.org"+orgNum+".example.com";
        return Grpc.newChannelBuilder(peerEndpoint, credentials)
                .overrideAuthority(OVERRIDE_AUTH)
                .build();
    }

    private static Identity newIdentity(String orgNum) throws IOException, CertificateException {
        Path cryptoPath = Paths.get("src/main/resources/org"+orgNum+".example.com");
        Path certDirPath = cryptoPath.resolve(Paths.get("users/User1@org"+orgNum+".example.com/msp/signcerts"));
        try (var certReader = Files.newBufferedReader(getFirstFilePath(certDirPath))) {
            String mspID = "Org"+ orgNum +"MSP";
            var certificate = Identities.readX509Certificate(certReader);
            return new X509Identity(mspID, certificate);
        }
    }

    private static Signer newSigner(String orgNum) throws IOException, InvalidKeyException {
        Path cryptoPath = Paths.get("src/main/resources/org"+orgNum+".example.com");
        Path keyDirPath = cryptoPath.resolve(Paths.get("users/User1@org"+orgNum+".example.com/msp/keystore"));
        try (var keyReader = Files.newBufferedReader(getFirstFilePath(keyDirPath))) {
            var privateKey = Identities.readPrivateKey(keyReader);
            return Signers.newPrivateKeySigner(privateKey);
        }
    }

    private static Path getFirstFilePath(Path dirPath) throws IOException {
        try (var keyFiles = Files.list(dirPath)) {
            return keyFiles.findFirst().orElseThrow();
        }
    }
}
